﻿namespace wToggl
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Threading.Tasks;

    using Windows.UI.Core;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Navigation;

    using wToggl.Common;

    using wToggle.Platform;
    using wToggle.Platform.Annotations;
    using wToggle.Platform.Data;
    using wToggle.Platform.Interfaces;

    using XPlatUtils;

    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class TimeEntryDetailPage : INotifyPropertyChanged
    {
        private NavigationHelper navigationHelper;

        private TimeEntry entry;

        private bool isLoading;

        private DateTime entryStartDate;

        private TimeSpan entryStartTime;

        private DateTime entryEndDate;

        private TimeSpan entryEndTime;

        private IEnumerable<Project> projects;

        private IEnumerable<Tag> tags;

        private IEnumerable selectedProjects;

        private IEnumerable selectedTags;

        private string entryDescription;

        public TimeEntryDetailPage()
        {
            this.InitializeComponent();

            this.navigationHelper = new NavigationHelper(this);
            this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
            this.navigationHelper.SaveState += this.NavigationHelper_SaveState;
        }

        /// <summary>
        /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
        /// </summary>
        public NavigationHelper NavigationHelper
        {
            get { return this.navigationHelper; }
        }

        /// <summary>
        /// Populates the page with content passed during navigation.  Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="sender">
        /// The source of the event; typically <see cref="NavigationHelper"/>
        /// </param>
        /// <param name="e">Event data that provides both the navigation parameter passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested and
        /// a dictionary of state preserved by this page during an earlier
        /// session.  The state will be null the first time a page is visited.</param>
        private async void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
        {
            if (e.NavigationParameter == null)
            {
                throw new InvalidOperationException("Empty navigation parameter passed to TimeEntryDetailPage");
            }

            if (!(e.NavigationParameter is ulong))
            {
                throw new ArgumentException("Navigation argument passed to TimeEntryDetailPage has incorrect type");
            }

            var settings = ServiceContainer.Resolve<IApplicationSettings>();
            var auth = ServiceContainer.Resolve<ITogglAuth>();

            if (!settings.HasLogin() || await auth.Authenticate(settings.Username, settings.Password) != AuthStatus.Success)
            {
                await this.Dispatcher.RunAsync(
                    CoreDispatcherPriority.Normal,
                    () =>
                    {
                        this.Frame.Navigate(typeof(LoginPage));
                    });

                return;
            }

            await this.LoadEntry((ulong)e.NavigationParameter);

            if (e.PageState == null)
            {
                return;
            }

            if (this.Entry == null)
            {
                return;
            }

            if (e.PageState.ContainsKey("description"))
            {
                this.EntryDescription = e.PageState["description"] as string;
            }

            if (e.PageState.ContainsKey("start"))
            {
                this.Entry.StartIso = e.PageState["start"] as string;
            }

            if (e.PageState.ContainsKey("duration"))
            {
                this.Entry.Duration = (int)e.PageState["duration"];
            }

            if (e.PageState.ContainsKey("projectId"))
            {
                var project = this.Projects.FirstOrDefault(p => p.Id == (ulong)e.PageState["projectId"]);

                if (project != null)
                {
                    this.SelectedProjects = new[] { project };
                }
                else
                {
                    this.SelectedProjects = null;
                }
            }

            if (e.PageState.ContainsKey("tags"))
            {
                var ids = ((string)e.PageState["tags"]).Split(',').Select(ulong.Parse).ToList();
                this.SelectedTags = this.Tags.Where(t => ids.Contains(t.Id));
            }

            this.OnPropertyChanged("Entry");
        }

        private async Task LoadEntry(ulong id, bool silent = false)
        {
            var entryProvider = ServiceContainer.Resolve<ITimeEntryProvider>();
            var tagProvider = ServiceContainer.Resolve<ITagProvider>();
            var projectProvider = ServiceContainer.Resolve<IProjectProvider>();

            this.IsLoading = true;
            this.Entry = await entryProvider.GetEntry(id);

            if (this.Entry != null)
            {
                this.EntryDescription = this.Entry.Description;
                this.Projects = await projectProvider.GetProjects(this.Entry.WorkspaceId);
                this.Tags = await tagProvider.GetTags(this.Entry.WorkspaceId);

                if (this.Projects != null)
                {
                    this.SelectedProjects = this.Projects.Where(p => p.Id == this.Entry.ProjectId);
                }

                if (this.Entry.TagIds != null && this.Tags != null)
                {
                    this.SelectedTags = this.Tags.Where(t => this.Entry.TagIds.Contains(t.Name));
                }
            }
            else
            {
                if (!silent)
                {
                    this.Dispatcher.ShowMessage(
                        "Time entry could not be loaded. Verify your internet connectivity and try again.",
                        "Toggly");
                }
                
                this.Projects = null;
                this.Tags = null;
                this.SelectedProjects = null;
                this.SelectedTags = null;
            }

            this.IsLoading = false;
        }

        public TimeEntry Entry
        {
            get
            {
                return this.entry;
            }

            set
            {
                this.entry = value;
                this.OnPropertyChanged();
            }
        }

        public bool IsLoading
        {
            get
            {
                return this.isLoading;
            }

            set
            {
                this.isLoading = value;
                this.OnPropertyChanged();
            }
        }

        public IEnumerable<Project> Projects
        {
            get
            {
                return this.projects;
            }

            set
            {
                this.projects = value;
                this.OnPropertyChanged();
            }
        }

        public IEnumerable<Tag> Tags
        {
            get
            {
                return this.tags;
            }

            set
            {
                this.tags = value;
                this.OnPropertyChanged();
            }
        }

        public IEnumerable SelectedTags
        {
            get
            {
                return this.selectedTags;
            }

            set
            {
                this.selectedTags = value;
                this.OnPropertyChanged();
            }
        }

        public IEnumerable SelectedProjects
        {
            get
            {
                return this.selectedProjects;
            }

            set
            {
                this.selectedProjects = value;
                this.OnPropertyChanged();
            }
        }

        public string EntryDescription
        {
            get
            {
                return this.entryDescription;
            }
            set
            {
                this.entryDescription = value;
                this.OnPropertyChanged();
            }
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
        /// <param name="e">Event data that provides an empty dictionary to be populated with
        /// serializable state.</param>
        private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
        {
            if (e.PageState == null)
            {
                return;
            }

            if (this.Entry == null)
            {
                return;
            }

            e.PageState["description"] = this.EntryDescription;
            e.PageState["start"] = this.Entry.StartIso;
            e.PageState["duration"] = this.Entry.Duration;

            if (this.SelectedTags != null)
            {
                e.PageState["tags"] = string.Join(",", this.SelectedTags.Cast<Tag>().Select(t => t.Id));
            }

            if (this.SelectedProjects != null && this.SelectedProjects.Cast<Project>().Any())
            {
                e.PageState["projectId"] = this.SelectedProjects.Cast<Project>().First().Id;
            }
        }

        #region NavigationHelper registration

        /// <summary>
        /// The methods provided in this section are simply used to allow
        /// NavigationHelper to respond to the page's navigation methods.
        /// <para>
        /// Page specific logic should be placed in event handlers for the  
        /// <see cref="NavigationHelper.LoadState"/>
        /// and <see cref="NavigationHelper.SaveState"/>.
        /// The navigation parameter is available in the LoadState method 
        /// in addition to page state preserved during an earlier session.
        /// </para>
        /// </summary>
        /// <param name="e">Provides data for navigation methods and event
        /// handlers that cannot cancel the navigation request.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            Analytics.ReportPageVisit(this);  
            this.navigationHelper.OnNavigatedTo(e);
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            this.navigationHelper.OnNavigatedFrom(e);
        }

        #endregion

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            this.Dispatcher.RunAsync(
                CoreDispatcherPriority.Normal,
                () =>
                    {
                        var handler = this.PropertyChanged;
                        if (handler != null)
                        {
                            handler(this, new PropertyChangedEventArgs(propertyName));
                        }
                    });
        }

        private void BackButton_OnClick(object sender, RoutedEventArgs e)
        {
            this.Frame.GoBack();
        }

        private async void ChangeTimeButton_OnClick(object sender, RoutedEventArgs e)
        {
            if (this.Entry == null)
            {
                this.Dispatcher.ShowMessage("Time entry could not be loaded - modification is disabled.", "Toggly");
                return;
            }

            var start = this.Entry.Start;
            var end = this.Entry.End;

            if (start == null || end == null)
            {
                return;
            }

            var dialog = new ManualEntryDialog(start.Value, end.Value);
            var result = await dialog.ShowAsync();

            if (result == ContentDialogResult.Primary)
            {
                this.Entry.StartIso = dialog.Start.ToUniversalTime().ToString("o");
                var duration = dialog.End.Subtract(dialog.Start);
                this.Entry.Duration = (int)Math.Truncate(duration.TotalSeconds);
                this.OnPropertyChanged("Entry");
            }
        }

        private async void SaveChangesButton_OnClick(object sender, RoutedEventArgs e)
        {
            if (this.Entry == null)
            {
                this.Dispatcher.ShowMessage("Time entry could not be loaded - modification is disabled.", "Toggly");
                return;
            }

            var provider = ServiceContainer.Resolve<ITimeEntryProvider>();

            var entryTags = this.SelectedTags != null ? this.SelectedTags.Cast<Tag>().ToList() : new List<Tag>();

            var projectId = 0UL;
            if (this.SelectedProjects != null)
            {
                var list = this.SelectedProjects.Cast<Project>().ToList();
                if (list.Any())
                {
                    projectId = list.First().Id;
                }
            }

            this.entry.ProjectId = projectId;
            this.entry.TagIds = entryTags.Select(t => t.Name).ToList();

            this.entry.Description = this.EntryDescription;
            await provider.Update(this.entry);
            this.Frame.GoBack();
        }
    }
}
